package koala.kuangjiademo.net;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import com.dhy.xintent.XCommon;
import com.google.gson.Gson;

import org.apache.commons.lang3.ArrayUtils;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import koala.kuangjiademo.BuildConfig;
import koala.kuangjiademo.data.AppSetting;
import koala.kuangjiademo.data.ResponsePacket;
import koala.kuangjiademo.data.User;
import koala.kuangjiademo.util.Helper;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
public class RequestManager {
    //region task
    //endregion
    //region net util
    /**
     * MUST set release host at the first place
     */
    private static final String[] HOSTS = {"http://ect.wwvas.com/vasms-web", "http://ecttest.wwvas.com/vasms-web", "http://192.168.0.156:8080/vasms-web", "http://192.168.0.123:8080/vasms-web"};
    private static String HOST;
    private static String IMAGE_HEADER;

    static {
        onHostUpdate(HOSTS[0]);
    }

    private static void onHostUpdate(String host) {
        HOST = host;
        IMAGE_HEADER = host.substring(0, host.indexOf("/vasms-web")) + "/img/";
    }

    public static String initHost(Context context) {
        if (BuildConfig.DEBUG) {
            String host = XCommon.getSetting(context, Setting.host);
            if (!TextUtils.isEmpty(host)) onHostUpdate(host);
        }
        return HOST;
    }

    public static String nextHost(Context context) {
        int i = ArrayUtils.indexOf(HOSTS, HOST);
        if (i != -1) {
            i = ++i % HOSTS.length;
        } else i = 0;
        onHostUpdate(HOSTS[i]);
        Toast.makeText(context, i + ":" + HOST, Toast.LENGTH_SHORT).show();
        XCommon.updateSetting(context, Setting.host, HOST);
        return HOST;
    }

    private enum Setting {
        host
    }

    private static <RESPONSE extends Serializable> Cancelable sendRequest(String url, HttpType type, Class<RESPONSE> responseCls, CallbackOne<RESPONSE> callback, Object... keyValue) {
        return sendRequest(url, type, responseCls, callback, keyValueMap(keyValue));
    }

    private static <RESPONSE extends Serializable> Cancelable sendRequest(String url, HttpType type, Class<RESPONSE> responseCls, CallbackOne<RESPONSE> callback, Map<String, Object> params) {
        return sendRequest(createRequest(url, type, params), responseCls, callback);
    }

    private static <RESPONSE extends Serializable> Cancelable sendRequest(String url, HttpType type, Class<RESPONSE> responseCls, CallbackOne<RESPONSE> callback, String json) {
        if (BuildConfig.DEBUG) {
            Log.i(TAG, "sendRequest: " + url + ",json " + json);
        }
        return sendRequest(createJsonRequest(url, type, json), responseCls, callback);
    }

    public static void download(final Context context, String url, final File file, final OnFileProgressListener listener) {
        Call call = client.newCall(new Request.Builder().url(url).build());
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                Helper.startRunnable(context, new Runnable() {
                    @Override
                    public void run() {
                        e.printStackTrace();
                        listener.onFinish(true, e.getMessage());
                    }
                });
            }

            @Override
            public void onResponse(Call call, okhttp3.Response response) throws IOException {
                if (response.isSuccessful()) {
                    ResponseBody body = response.body();
                    RandomAccessFile rafile = new RandomAccessFile(file, "rwd");
                    int length;
                    long finished = 0, total = body.contentLength();
                    rafile.seek(0);
                    byte[] datas = new byte[1024];
                    InputStream inputStream = body.byteStream();
                    while ((length = inputStream.read(datas)) != -1) {
                        finished += length;
                        rafile.write(datas, 0, length);
                        postOnProgress(finished, total);
                    }
                    rafile.close();
                    inputStream.close();
                    Helper.startRunnable(context, new Runnable() {
                        @Override
                        public void run() {
                            listener.onFinish(false, null);
                        }
                    });
                } else {
                    onFailure(call, new IOException(response.message()));
                }
            }

            void postOnProgress(final long progress, final long total) {
                Helper.startRunnable(context, new Runnable() {
                    @Override
                    public void run() {
                        listener.onProgress(progress, total);
                    }
                });
            }
        });
    }

    private static <RESPONSE extends Serializable> Cancelable sendRequest(final Request.Builder builder, final Class<RESPONSE> responseCls, final CallbackOne<RESPONSE> callback) {
        if (!TextUtils.isEmpty(user.token)) builder.header("token", user.token);
        final Call call = client.newCall(builder.build());
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                callback.onCallback(new Response(null, null, getNetErrorMessage(e)));
            }

            @Override
            public void onResponse(Call call, okhttp3.Response response) throws IOException {
                if (response.isSuccessful()) {
                    callback.onCallback(parseResponse(response.body().string(), responseCls));
                } else {
                    String msg;
                    if (BuildConfig.DEBUG) {
                        msg = "failed with HTTP" + response.code() + ", " + response.message();
                    } else {
                        msg = MSG_REQUEST_FAILED_TIP;
                    }
                    callback.onCallback(new Response(null, null, msg));
                }
            }
        });
        return new OkHttpCancelable(call);
    }

    @NonNull
    private static <RESPONSE extends Serializable> Response parseResponse(String json, Class<RESPONSE> responseCls) {
        try {
            if (responseCls.equals(String.class)) {
                return new Response(json, json, null);
            } else {
                Gson gson = Helper.getGson();
                return new Response(json, gson.fromJson(json, responseCls), null);
            }
        } catch (Exception e) {
            if (BuildConfig.DEBUG) {
                System.out.println("*************** parseResponse error ***************");
                System.out.println("responseCls:" + responseCls);
                System.out.println("json:" + json);
                e.printStackTrace();
                return new Response(json, null, "ParseResponse error\n" + responseCls.getName());
            } else {
                return new Response(json, null, MSG_REQUEST_FAILED_TIP);
            }
        }
    }

    private static String getNetErrorMessage(@Nullable Exception error) {
        if (BuildConfig.DEBUG && error != null) {
            Log.d(TAG, "getNetErrorMessage: ");
            error.printStackTrace();
        }
        if (error instanceof TimeoutException) {
            return MSG_REQUEST_TIMEOUT;
        } else {//null || UnknownHostException, ...
            if (BuildConfig.DEBUG) {
                return error != null ? error.getMessage() : "null";
            } else {
                return MSG_REQUEST_FAILED_TIP;
            }
        }
    }

    private static final String MSG_REQUEST_FAILED_TIP = "服务器异常，请稍候重试";
    private static final String MSG_REQUEST_TIMEOUT = "网络超时，请重试";
    private static final String TAG = "TAG";
    private static final OkHttpClient client = new OkHttpClient().newBuilder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(10, TimeUnit.MINUTES)
            .writeTimeout(60, TimeUnit.SECONDS)
            .build();

    private static Map<String, Object> keyValueMap(final Object... keyValue) {
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < keyValue.length; i += 2) {
            map.put(keyValue[i].toString(), keyValue[i + 1]);
        }
        return map;
    }

    private static String parseToString(Object v) {
        if (v instanceof TextView) {
            return ((TextView) v).getText().toString();
        } else if (v != null) {
            return v.toString();
        } else return "";
    }

    private static Request.Builder createRequest(String url, HttpType type, Map<String, Object> params) {
        url = getFullUrl(url);
        if (type == HttpType.GET) {
            return createGetRequest(url, params);
        } else if (type == HttpType.POST_FILE) {
            return createUploadRequest(url, params);
        } else {
            return createSimpleRequest(url, type, params);
        }
    }

    private static Request.Builder createJsonRequest(String url, HttpType type, String json) {
        RequestBody body = createJsonBody(json);
        if (type == HttpType.POST) {
            return new Request.Builder().url(getFullUrl(url)).post(body);
        } else if (type == HttpType.PUT) {
            return new Request.Builder().url(getFullUrl(url)).put(body);
        } else {
            throw new IllegalArgumentException("unkown Json Request type:" + type.name());
        }
    }

    private static String getFullUrl(String url) {
        return url.startsWith("http") ? url : HOST + url;
    }

    private static Request.Builder createSimpleRequest(String url, HttpType type, Map<String, Object> params) {
        RequestBody body = createFormBody(params);
        Request.Builder builder = new Request.Builder().url(url);
        switch (type) {
            case POST:
                builder.post(body);
                break;
            case PUT:
                builder.put(body);
                break;
            case DELETE:
                builder.delete(body);
                break;
        }
        return builder;
    }

    private static RequestBody createFormBody(Map<String, Object> params) {
        FormBody.Builder formBuilder = new FormBody.Builder();
        for (String key : params.keySet()) {
            formBuilder.add(key, parseToString(params.get(key)));
        }
        return formBuilder.build();
    }

    private static RequestBody createJsonBody(String json) {
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json);
    }

    private static Request.Builder createGetRequest(String url, Map<String, Object> params) {
        StringBuilder sb = new StringBuilder();
        for (String key : params.keySet()) {
            try {
                String value = URLEncoder.encode(parseToString(params.get(key)), "UTF-8");
                sb.append("&").append(key).append("=").append(value);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        if (sb.length() != 0) {
            sb.deleteCharAt(0);
            sb.insert(0, "?");
        }
        return new Request.Builder().url(url + sb.toString());
    }

    private static Request.Builder createUploadRequest(String url, Map<String, Object> params) {
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        for (String key : params.keySet()) {
            Object value = params.get(key);
            if (value instanceof File) {
                File file = (File) value;
                RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
                builder.addFormDataPart(key, file.getName(), fileBody);
            } else {
                builder.addFormDataPart(key, parseToString(value));
            }
        }
        return new Request.Builder().url(url).post(builder.build());
    }

    private static class CallbackOne<DATA extends Serializable> {
        private OnResponseListener listener;

        CallbackOne(OnResponseListener listener) {
            this.listener = listener;
        }

        void onCallback(Response<DATA> r) {
            if (r.data instanceof ResponsePacket) {
                ResponsePacket packet = (ResponsePacket) r.data;
                r.status.error_code = packet.getCode();
                r.status.message = packet.getMessage();
            }
            listener.onThreadResponse(r.status, r.data);
        }
    }

    private static class Response<DATA extends Serializable> {
        ResponseStatus status;
        DATA data;

        Response(String json, DATA data, String errorMessage) {
            this.data = data;
            status = new ResponseStatus();
            status.rawResponse = json;
            status.setMessage(errorMessage);
        }
    }

    public static String getImageFullUrl(String tail) {
        return IMAGE_HEADER + tail.replaceAll("\\\\", "/");
    }

    private static class OkHttpCancelable implements Cancelable {
        private Call call;

        OkHttpCancelable(Call call) {
            this.call = call;
        }

        @Override
        public void cancel() {
            call.cancel();
        }

        @Override
        public boolean isCanceled() {
            return call.isCanceled();
        }
    }

    private static String objectToJson(Object obj) {
        return Helper.getGson().toJson(obj);
    }

    private static class ResponseStatus implements IResponseStatus {
        transient String rawResponse;
        private int error_code;
        private String message;
        private static final int LOCALL_ERROR = -1;

        void setMessage(String message) {
            if (!TextUtils.isEmpty(message)) {
                this.message = message;
                error_code = LOCALL_ERROR;
            }
        }

        public boolean isSuccess() {
            return error_code == 0;
        }

        @Override
        public String getRawResponse() {
            return rawResponse;
        }

        @Override
        public int getCode() {
            return error_code;
        }

        public String getMessage() {
            return message;
        }
    }

    private static final User user = new User();

    public static void updateUser(Context context, @Nullable User user) {
        XCommon.updateSetting(context, AppSetting.user, user);
        RequestManager.user.update(user);
        Log.i(TAG, "updateUser " + Helper.getGson().toJson(user));
    }
    //endregion
}
